package com.bmps.springcloud.insuredmicroservice.controller.cancel;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bmps.springcloud.insuredmicroservice.constant.accept.AcceptConstant;
import com.bmps.springcloud.insuredmicroservice.constant.accept.MultConstant;
import com.bmps.springcloud.insuredmicroservice.constant.accept.QuotaConstant;
import com.bmps.springcloud.insuredmicroservice.constant.cancel.CancelConstant;
import com.bmps.springcloud.insuredmicroservice.model.accept.response.ErrorListModel;
import com.bmps.springcloud.insuredmicroservice.model.accept.response.ErrorModel;
import com.bmps.springcloud.insuredmicroservice.model.accept.response.ResponseHeaderModel;
import com.bmps.springcloud.insuredmicroservice.model.accept.resquest.AcceptRequestDataModel;
import com.bmps.springcloud.insuredmicroservice.model.cancel.*;
import com.bmps.springcloud.insuredmicroservice.model.cancel.coresystem.PrtNoModel;
import com.bmps.springcloud.insuredmicroservice.model.cancel.coresystem.RequestBodyModel;
import com.bmps.springcloud.insuredmicroservice.model.cancel.coresystem.RequestModel;
import com.bmps.springcloud.insuredmicroservice.service.cancelpolicy.CancelPolicyService;
import com.bmps.springcloud.insuredmicroservice.service.webservice.WebService;
import com.bmps.springcloud.insuredmicroservice.util.XmlUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * description:
 * 投保单撤件
 *
 * @author jackdaw
 * @date 2019-11-26 17:48
 */
@RestController
@RequestMapping(value = "/insured")
public class CancelPolicyController {


    private final CancelPolicyService cancelPolicyService;

    private final RedissonClient redissonClient;

    private final WebService webService;

    private final RedisTemplate redisTemplate;

    private Logger logger = LoggerFactory.getLogger(CancelPolicyController.class);

    public CancelPolicyController(CancelPolicyService cancelPolicyService, RedissonClient redissonClient,
                                  WebService webService, RedisTemplate redisTemplate) {
        this.cancelPolicyService = cancelPolicyService;
        this.redissonClient = redissonClient;
        this.webService = webService;
        this.redisTemplate = redisTemplate;
    }

    @PostMapping(value = "/cancelpolicy", headers = "Content-Type=application/json;charset=UTF-8")
    public CancelResponseModel cancelPolicy(@Valid @RequestBody CancelRequestModel cancelRequestModel, BindingResult bindingResult) {
        //参数校验
        if (bindingResult.hasErrors()) {
            return this.bindingErrors(cancelRequestModel.getHeader(), bindingResult);
        }

        return this.dealDate(cancelRequestModel);
    }

    private CancelResponseModel dealDate(CancelRequestModel cancelRequestModel) {
        //保单号
        String contNo = cancelRequestModel.getRequest().getContNo();
        logger.info("撤单处理保单号=" + contNo);
        //获取当前单操作锁key=CANCEL|contNo
        RLock lock = redissonClient.getLock(CancelConstant.PREFIX_CANCEL + contNo);
        boolean locked = false;
        try {
            locked = lock.tryLock(5000, 5000, TimeUnit.MILLISECONDS);
            if (locked) {
                return this.cancel(cancelRequestModel);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            logger.error("保单号" + contNo + " 撤单请求获取锁失败", e.getMessage());
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

        return null;
    }

    private CancelResponseModel cancel(CancelRequestModel cancelRequestModel) {
        //保单号
        String contNo = cancelRequestModel.getRequest().getContNo();
        //投保单号
        String prtNo = cancelRequestModel.getRequest().getPrtNo();
        CancelResponseModel cancelResponseModel = createDefaultObject(cancelRequestModel);
        ResultInfoModel resultInfoModel = cancelResponseModel.getResponse().getResultInfo().get(0);
        //查询保单信息 map： state-当前单状态,pushflag-推送状态,request-受理报文
        Map<String, Object> info = cancelPolicyService.getPolicyInfo(contNo);
        if (info == null || info.size() == 0) {
            resultInfoModel.setResultCode("N");
            resultInfoModel.setResultMessage(CancelConstant.FAIL_NO_DATA);
            return cancelResponseModel;
        } else {
            //校验投保单号与受理报文中数据是否一致
            if (!this.checkPrtNo(info.get(CancelConstant.REQUEST).toString(), prtNo)) {
                resultInfoModel.setResultCode("N");
                resultInfoModel.setResultMessage(CancelConstant.FAIL_PRTNO_ERROR);
                return cancelResponseModel;
            }
            //判断当前单是否已经推送核心，如果推送核心需要请求核心撤单接口
            int pushFlag = (int) info.get(CancelConstant.PUSH_FLAG);
            //没有推送核心-0,3,4或核心没有成功受理-2 只需要更改数据库状态
            if (pushFlag == CancelConstant.PUSH_FLAG_ZERO || pushFlag == CancelConstant.PUSH_FLAG_TWO ||
                   pushFlag == CancelConstant.PUSH_FLAG_THREE || pushFlag == CancelConstant.PUSH_FLAG_FOUR) {
                String flag;
                if (!StringUtils.equals(flag = updateData(contNo, (int) info.get(CancelConstant.STATE)), CancelConstant.Y)) {
                    resultInfoModel.setResultCode("N");
                    resultInfoModel.setResultMessage(flag);
                    return cancelResponseModel;
                } else {
                    resultInfoModel.setResultMessage(String.format(CancelConstant.SUCCESS_MESSAGE, contNo));
                    // 删除该保单号的累计风险保额缓存
                    this.deleteRedisQuota(contNo);
                    return cancelResponseModel;
                }
                //推送过核心且返回成功，请求核心撤单
            } else if (pushFlag == CancelConstant.PUSH_FLAG_ONE) {
                //生成请求核心的报文
                String requestXmlString = XmlUtil.beanToXml(this.createObjectCallCore(cancelRequestModel));
                logger.info("请求核心报文" + requestXmlString);
                //发起请求
                CancelResponseModel response = this.requestCoreSystem(requestXmlString);
                //请求异常
                if (response.getHeader() == null) {
                    resultInfoModel.setResultCode("N");
                    resultInfoModel.setResultMessage(CancelConstant.FAIL_REQUEST_CORE);
                    return cancelResponseModel;
                } else {
                    //解析返回报文是否成功
                    if (analyzeResult(response)) {
                        String flag;
                        if (!StringUtils.equals(flag = updateData(contNo, (int) info.get(CancelConstant.STATE)), CancelConstant.Y)) {
                            resultInfoModel.setResultCode("N");
                            resultInfoModel.setResultMessage(flag);
                            return cancelResponseModel;
                        }
                        // 删除该保单号的累计风险保额缓存
                        this.deleteRedisQuota(contNo);
                    }
                    return response;
                }
            }
        }

        return cancelResponseModel;
    }

    private boolean analyzeResult(CancelResponseModel response) {
        return (StringUtils.equals(response.getHeader().getResponseCode(), CancelConstant.Y)
                && StringUtils.equals(response.getResponse().getResultInfo().get(0).getResultCode(), CancelConstant.Y));
    }

    private String updateData(String contNo, int state) {

        try {
            if (cancelPolicyService.cancelPolicy(contNo, state) == 0) {
                return CancelConstant.FAIL_NO_DATA;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("保单号" + contNo + "更新数据库异常:" + e.getMessage());
            return CancelConstant.EXCEPTION_DATABASE + e.getMessage();
        }
        return CancelConstant.Y;
    }

    private CancelResponseModel createDefaultObject(CancelRequestModel cancelRequestModel) {
        CancelResponseModel cancelResponseModel = new CancelResponseModel();
        ResponseHeaderModel responseHeaderModel = new ResponseHeaderModel();
        BeanUtils.copyProperties(cancelRequestModel.getHeader(), responseHeaderModel);
        responseHeaderModel.setResponseCode("Y");
        cancelResponseModel.setHeader(responseHeaderModel);
        ResultInfoModel resultInfoModel = new ResultInfoModel();
        resultInfoModel.setPrtNo(cancelRequestModel.getRequest().getPrtNo());
        resultInfoModel.setResultCode("Y");
        ResultListModel resultListModel = new ResultListModel();
        List<ResultInfoModel> arrayList = new ArrayList<>(1);
        arrayList.add(resultInfoModel);
        resultListModel.setResultInfo(arrayList);
        cancelResponseModel.setResponse(resultListModel);
        return cancelResponseModel;
    }

    private CancelResponseModel requestCoreSystem(String request) {
        String response = webService.callService(request, CancelConstant.CANCEL_SERVICE_NAME, CancelConstant.CANCEL_SYMBOL);
        logger.info("请求核心响应结果" + response);
        if (StringUtils.isBlank(response) || !response.contains(CancelConstant.XML_PACKAGE)) {
            return new CancelResponseModel();
        } else {
            return (CancelResponseModel) XmlUtil.xmlToBean(CancelResponseModel.class, response);
        }
    }

    /**
     * 检验受理数据的投保单号和撤件请求是否一致
     *
     * @param message 受理报文
     * @param prtNo   撤件投保单号
     * @return boolean
     */
    private boolean checkPrtNo(String message, String prtNo) {

        AcceptRequestDataModel acceptRequestDataModel = JSONObject.toJavaObject(JSON.parseObject(message),
                AcceptRequestDataModel.class);
        return StringUtils.equals(prtNo, acceptRequestDataModel.getRequest().getPolicyInfo().getPrtNo());
    }

    private CancelResponseModel bindingErrors(RequestHeaderModel requestHeaderModel, BindingResult bindingResult) {
        CancelResponseModel cancelResponseModel = new CancelResponseModel();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        List<ErrorModel> errorList = new ArrayList<>(fieldErrors.size());
        for (FieldError error : fieldErrors) {
            ErrorModel errorModel = new ErrorModel();
            errorModel.setErrorMessage(error.getDefaultMessage());
            errorModel.setErrorId(AcceptConstant.RESULT_FAIL_CODE);
            errorList.add(errorModel);
        }
        ErrorListModel errorListModel = new ErrorListModel();
        errorListModel.setError(errorList);
        ResponseHeaderModel responseHeaderModel = new ResponseHeaderModel();
        responseHeaderModel.setErrorList(errorListModel);
        BeanUtils.copyProperties(requestHeaderModel, responseHeaderModel);
        responseHeaderModel.setResponseCode(CancelConstant.N);
        cancelResponseModel.setHeader(responseHeaderModel);
        return cancelResponseModel;
    }

    private RequestModel createObjectCallCore(CancelRequestModel cancelRequestModel) {
        RequestModel requestModel = new RequestModel();
        RequestHeaderModel headerModel = new RequestHeaderModel();
        BeanUtils.copyProperties(cancelRequestModel.getHeader(), headerModel);
        RequestBodyModel requestBodyModel = new RequestBodyModel();
        List<PrtNoModel> arr = new ArrayList<>();
        PrtNoModel prtNoModel = new PrtNoModel();
        prtNoModel.setPrtNo(cancelRequestModel.getRequest().getPrtNo());
        arr.add(prtNoModel);
        requestBodyModel.setPrtNos(arr);
        requestModel.setHeader(headerModel);
        requestModel.setRequest(requestBodyModel);
        return requestModel;
    }

    private void deleteRedisQuota(String contno) {
        try {
            Set<String> keys = redisTemplate.keys("*|" + contno + "|" + QuotaConstant.QUOTA);
            Set<String> multKeys = redisTemplate.keys("*|" + contno + "|*|" + MultConstant.MULT);
            Set<String> setAll = new HashSet<>();
            if (CollectionUtils.isNotEmpty(keys)) {
                setAll.addAll(keys);
            }
            if (CollectionUtils.isNotEmpty(multKeys)) {
                setAll.addAll(multKeys);
            }
            if (CollectionUtils.isNotEmpty(setAll)) {
                redisTemplate.delete(setAll);
            }
        } catch (Exception e) {
            logger.error("删除保单号" + contno + "累计风险保额或份数缓存失败");
            e.printStackTrace();
        }
    }
}
